日10亿级处理,基于云的微服务架构
德比软件:基于云的微服务架构
作者:朱攀,德比软件架构师,同济大学研究生,2007 年 2 月加入德比软件(DerbySoft),拥有 10 年以上的软件架构和开发经验。目前主要负责公司数据对接平台的架构、基础设施建设、技术团队管理及公司技术的布道。作为德比软件的早期员工,从无到有地主导了德比软件数据对接平台的架构设计和实现,完成了数据对接平台多个版本的架构改进和升级,数据对接平台每天处理的 API 调用从 0 增长到现在的 10 亿多。期间设计实现了很多必要的基础设施和服务,如公司内部的 RPC 框架 DerbySoft-RPC、智能路由服务 Router、分布式数据存储服务 DStorage、网关服务 DGateway 等。目前主要关注基础架构、微服务、大数据存储和技术团队建设。
10.1 概述
微服务概念被提出来以后,短时间内就成了互联网圈的一个技术热点,有很多互联网公司计划或正在进行微服务化改造,那么应该怎么开始实施微服务呢?需要哪些基础设施来支持呢?我所在的公司德比软件从 2009 年就开始采用面向服务架构(SOA)来重新实现数据对接平台,我们积累了丰富的面向服务架构的经验,而微服务架构是面向服务架构思想的一种实现,只是服务职责更单一,粒度更小,过程更自动化。我们在多年的服务化架构实践过程中,已经不自觉地将服务的粒度细化了。本文想分享德比软件在实施微服务架构过程中积累的一些基础设施及这些基础设施解决的痛点问题,希望对大家有所帮助和启发。
10.2 德比软件数据对接平台的架构
数据对接平台的架构大体上分三个层次,如图 10.1 所示,最外面是 API 层,由 API Gateway(网关)和各业务的 API 服务组成;中间层是服务层(Service),平台的大部分功能在这一层实现,服务之间可以通过依赖或组合组成功能更强的服务;底层是数据存储层(Data Storage),由缓存层和存储层组成。数据对接平台采用微服务架构,为了支撑这个架构,我们提供了必要的基础设施来保障上层各个服务和应用的可用性。
图 10.1
10.3 德比软件微服务架构基础设施
如图 10.2 所示,将基础设施分为了八大部分,分别是服务框架、基础服务、API 网关、自动化、服务降级、监控报警(服务健康状态)、日志处理和调用链追踪。其中,
服务框架、基础服务、API 网关和服务降级的作用是提高服务的连续工作时间;
自动化可以简化开发和部署过程;
监控报警、日志处理和调用链追踪可以帮助快速发现问题、定位问题并解决问题。
图 10.2
基础设施全景图如图 10.3 所示。
图 10.3
10.4 API 网关
API 网关是微服务架构中不可或缺的部分。API 网关是对外提供的服务,是系统的入口,所有的外部系统都需要通过 API 网关来接入。Dgateway 提供安全认证、流控、路由、 API 版本管理等功能。流控维度分为用户类型、用户来源、IP、业务 API 等,保护服务或资源不被滥用或攻击。DGateway 基于开源软件 Tyk 做了二次开发。
10.5 服务框架
服务框架分两部分,高可用的 RPC(DerbySoft-RPC)和服务依赖管理,如图 10.4 所示。
图 10.4
10.5.1 高可用 RPC
RPC 是微服务架构中最重要的基础组件,一个健壮的 RPC 能有效降低实施微服务架构的成本。RPC 封装了远程调用的复杂细节,让服务使用方感觉像调用本地方法一样调用远程方法,服务提供方感觉像实现本地接口一样来实现服务。DerbySoft-RPC 分为 Client 和Server 两部分。Client 主要有以下功能。
客户端故障检测,识别并标示太慢或崩溃了的服务器。
熔断机制,如果发现服务超时比例过限,则启动熔断,客户端会快速返回失败,以减轻服务端的压力,定时允许部分请求通过,根据请求返回的健康程度来决定是否恢复熔断。
负载均衡策略,通过记录到每个节点的未完成请求数来决定下一个请求发到哪里。容错机制,对某些错误自动重试,比如连接的服务节点不可用时可以自动将请求再次发送到其他节点,可定义重试次数。
序列化和反序列化,选择序列化工具最重要的考量点有性能高低、是否多语言支持、序列化后的消息大小、是否向前兼容等。我们尝试过各种序列化工具,而 Protocol Buffer 在性能和空间占用上都表现优异,是我们使用最频繁的序列化工具。对敏感数据加密,比如信用卡等敏感信息,需要在 Client 端加密传输。
超时管理,可设定每个请求的超时时间。
Server 部分相对简单,主要提供超时管理、序列化和反序列化、敏感信息解密、队列管理、线程管理等功能。
刚才提到选择序列化工具时需要考虑是否支持多语言,这一点很重要,RPC 最好能在多语言环境下实现,这样能让技术团队突破编程语言限制,使技术栈更丰富灵活。我们在这方面是有过教训的。
在公司早期,大概是 2009 年,当时的业务量不大,在语言上主要使用 Java,为了方便我们在第一个 RPC 版本上采用了 Java 序列化机制,却导致后面想要更换编程语言时大受限制。因此我们做了第二版 RPC,将序列化机制换掉,采用 Java 的字节序以兼容第一个版本。DerbySoft-RPC 是第三版 RPC,实现了对 Go、Scala 和 Java 三种语言的兼容。原则上我们不限制服务的实现语言,只要能按要求实现服务接口并满足性能要求即可。微服务化后的一个好处就是根本不用担心使用小众的编程语言,如果每个服务的粒度足够小,那么最坏的情况就是在没人能维护这个小众语言的服务时,需要花点时间用熟悉的语言来重写。
10.5.2 服务依赖管理
微服务化之后,系统面临的一个突出问题是服务虽小,但是数量极多。如果这些服务全部在一个可用区内,那么服务之间的依赖还比较好管理,可以做服务自动注册和发现来实现依赖管理,但是如果服务分布在很多可用区中,尤其是分布在跨国跨地区的可用区之间,则其依赖和调用的管理就会比较麻烦。
另外,跨可用区服务之间的调用还需要考虑通信安全的问题,对每个服务设置不同的特殊端口,如果需要跨区域调用服务,那么配置访问权限也是一件麻烦的事情。
众多服务的依赖示例如图 10.5 所示。
图 10.5
图 10.5 所示的服务依赖是不是很乱?我们的服务节点分布在全球 14 个可用区,大部分在亚马逊云上,小部分在自建的私有云里,可用区之间的服务有复杂的依赖关系。为了解决这种情况下服务之间的依赖调用问题、API 安全问题和服务的高可用问题,我们设计开发了路由服务,以解耦服务的调用者和提供者,简化网络拓扑及服务器的安全配置。引入路由之后,简化后的服务之间的依赖关系如图 10.6 所示。
图 10.6
所有的服务只和路由通信,服务之间不再相互依赖,每个服务只需要依赖并维护自己所在的可用区路由节点,路由会找到调用的服务目的地。绝大部分情况下,它在本区域内就能找到相应的服务,不需要跨区,这取决于服务的部署情况。为了提高响应速度,每个可用区都需要部署关键的服务,除非本可用区的服务崩溃了,否则在这种情况下不会出现跨区访问。有些服务不是那么重要,可能只会在某个或某几个可用区中部署,这时也可能会出现服务跨可用区调用的情况。
这样不仅更好地管理了依赖,解决了服务器可用区调用问题,还解决了 API 访问安全问题:每个可用区建立一个 VPC(虚拟私有云),所有的服务都在 VPC 内,VPC 内的 API 调用可忽略安全验证,跨 VPC 路由节点之间用安全组来限制 IP 白名单访问,只允许路由节点可以跨可用区访问其他 VPC 内的路由节点,服务访问路由或者路由访问服务都必须在同一个 VPC 内使用内网地址访问。
10.6 基础服务
基础服务分为四部分,分别是配置中心、安全数据服务、数据存储服务、订单服务,如图 10.7 所示。基础服务大多和存储有关,提供高性能、高可用的服务。
图 10.7
10.6.1 配置中心
配置中心提供高可用的配置数据存储服务,几乎所有的应用和服务都依赖配置中心。配置中心主要解决基础数据和配置数据的单点依赖及节点之间的数据同步问题,以便更好地支持各个服务的水平伸缩。配置中心可以配置主键、索引字段、唯一约束、每个字段的类型约束等,其功能类似于数据库,开放全量和变量的同步接口,提供多语言的客户端 SDK,同步全量或变量数据,根据服务端的配置,在内存中建立数据索引,缓存数据,并在本地文件中持久化缓存的备份,如图 10.8 所示。
图 10.8
一个依赖配置中心的服务,在启动时会先查找本地文件中有没有配置数据的缓存,如果有,则从文件中加载配置数据到内存中,启动服务,接着调用变量接口对比本地数据和配置中心的数据是否是同一版本,如果数据有变化,则更新数据到本地缓存。如果本地文件中没有配置数据的缓存,则先调用一次全量接口缓存所有配置数据到本地缓存。即使配置中心的所有节点都无法运行,也不影响各个依赖它服务的现有节点的正常工作。
因为每个服务都会缓存依赖的配置数据,所以对配置中心的性能要求不是太高,我们用 AWS RDS 来解决配置数据存储的高可用问题。AWS RDS 是一个托管关系数据库服务。
10.6.2 安全数据服务
高可用的安全数据服务提供数据加密和解密服务,定时清理过期的数据,解决敏感数据的安全临时存储和传输问题,比如银行卡和信用卡信息的临时存储和传输。需要传输敏感信息时,在数据传输方调用数据加密服务的加密接口,并明确信息有效时间,加密服务会将信息加密后存储,并返回一个全局唯一 ID,数据传输方发送这个唯一 ID 给数据接收方,如果数据接收方收到这个 ID 之后,调用数据解密接口,则加密服务会根据 ID 找到相应的数据,返回解密之后的数据。
10.6.3 数据存储服务
Dstorage 提供高可用的酒店动态数据存储服务,可以解决酒店海量动态数据存储问题,提供高性能的数据存取接口,提供变量数据的同步接口。根据性能和容量需求,用户可选的存储引擎有 Redis、Codis、Ardb、DynamoDB 等。DStorage 支持多可用区部署,通过变化发现服务,实现多可用区的数据同步,保证数据最终一致性。
Dcontent 提供高可用的酒店静态数据缓存服务可以解决酒店海量静态数据的缓存问题。底层支持的存储引擎有 Ardb、Redis 等。DContent 支持一主多从多可用区部署,也支持多主多可用区部署,保证数据最终一致性。
AWS S3 提供高可用的文件存储服务,可以存储文件数据,如图片、视频、日志文件、备份数据等。
10.6.4 订单服务
由于订单数据比较特殊,所以将与订单相关的功能单独出来,作为独立服务。订单服务聚合所有系统节点的订单信息,让订单比较容易追溯,提供高可用的订单存储和查询服务,解决订单数据的单点存储问题,更好地支持其他依赖订单数据的系统进行水平扩展和迁移。订单服务对可用性要求极高,底层存储依赖 AWS RDS,在全球每个可能用到订单服务的可用区部署订单服务集群。
10.7 服务降级
根据业务要求对每个服务及其依赖的资源按重要程度进行分级,限制每个依赖服务的超时时间,定义各级服务的 SLA 指标,对强依赖的基础服务或资源实行更高的 SLA 标准,根据 SLA 指标制定容错方案。
制定服务自动降级策略,设置服务开关,关键时刻“弃车保帅”,对弱依赖的服务进行降级,比如将日志服务降级以保障重要的服务不受影响。
对超大数据或流量用户进行隔离,在隔离的环境中为这部分用户提供独立的服务,避免影响其他正常用户服务。
10.8 自动化
微服务化后,服务的数量很多,为了节约开发成本,必须尽可能地自动化完成一些有固定步骤的工作,如自动化测试和自动化部署,如图 10.9 所示。
图 10.9
1.自动化测试。
单元测试自动化:我们目前要求单元测试覆盖率大于 90%,通过 Jenkins 整合 Sonar。每次提交代码后自动运行单元测试,Sonar 会自动给出代码评分,如果测试覆盖率不够或代码不达标,则单元测试失败。
性能测试自动化、平台化:我们开发了自动化测试平台 DTesting,关键服务的每次变更都需要在平台上进行性能测试,并生成性能测试报告,对比历史的性能测试数据。
功能测试自动化、平台化:每个应用都有完整的功能测试用例和脚本,可自动化回归功能测试。
2.自动化部署。
我们引入了 Docker,以减少人为部署时的误操作。
10.9 日志处理
日志处理分为两部分:一部分是实时日志处理,另一部分是冷日志处理。所有的应用日志都需要写入 Kafka 和文件,Kafka 中所有的实时日志都会被 DLog 系统及时消费处理,但是实时日志的保留时间不长,一般保留一个月。写入文件的所有日志会被定时备份到 AWS S3 上永久保留,如果需要一个月前甚至更久以前的系统日志,则可以在 DLog 系统中选择需要的日志范围,DLog 会将符合条件的日志从 S3 上下载下来并重新建立日志索引。日志主要是为监控提供基础源数据,为定位问题提供依据,如图 10.10 所示。
图 10.10
10.10 调用链追踪
随着服务的粒度越来越小,系统的服务变得越来越多,不同的服务可能由不同的团队实现,一个服务的实现可能会依赖几个甚至十几个服务。对于出现问题后,如何快速准确地定位故障,如何追踪业务的处理顺序和结果,业内已经有了一些实践和解决方案,Google Dapper 是 Google 研发的分布式跟踪系统,Google 也为此发表论文阐述了分布式跟踪系统的理论基础,给分布式跟踪的实现提供了非常好的参考示范。对于 Java 编写的服务,我们采用 Zipkin 来做调用链跟踪,Zipkin 是 Google Dapper 系统的开源实现,采用 Scala 编写;
而对于 Go 语言实现的服务,我们目前还没有好的解决方案。一个好的调用链追踪系统能为定位和排查故障提供强有力的支持。对于微服务架构,调用链追踪是必备的基础设施。
10.11 服务健康状态
很多情况下,解决故障可能很快,难点在于发现故障和定位故障,这需要我们的系统有全方位的监控和报警能力。基于业务和技术对可用性的需求,我们开发了服务健康状态监控和报警系统,从业务层、服务层、硬件基础设施层分别进行监控和报警,如图 10.11 所示。
10.11.1 报警
报警系统定义所有报警种类、报警级别、处理流程,并提供报警接入机制,各个层次的监控都可以通过自定义报警引擎接入报警系统来触发报警。7 × 24 小时服务监控团队会根据各个报警的处理流程,依次联系最高优先级的处理人来处理故障,如果在规定时间内没有处理完毕,则系统会根据报警的种类和级别自动升级报警响应级别,提高处理优先级来保障重要的报警能得到及时处理。
图 10.11
10.11.2 监控
监控分为硬件基础设施层监控、服务层监控和业务层监控三大类。
硬件基础设施层监控,包括操作系统、内存、CPU、网络流量和状态、连接数等方面的监控。我们用 Zabbix 来做基础设施层的监控,通过 Zabbix 的 API 将数据整合进我们的监控系统 DMonitor。
服务层监控,包括基础组件监控、基础服务监控、常规服务监控和外部接口调用监控。其中,基础组件监控包括对 Etcd、AWS RDS、Redis、Docker、Kafka 等基础组件使用情况的监控;基础服务监控包括对配置中心服务、认证服务、安全存储服务、路由服务、API Gateway 等关键服务的监控;常规服务监控则是监控所有一般服务的状态;而外部接口调用监控是监控分析系统依赖的外部访问情况,包括访问量、错误信息、超时状况等,为快速定位问题提供依据。
业务层监控,是指从业务角度出发,收集和分析业务层的访问量,根据各个业务的历史数据和已知的影响因素预测现在和未来的业务情况,定义监控报警目标。比如,在最近半个小时内某个业务的请求量小于预期值,在最近 1 个小时内对某个客户的 API 调用次数超过预期值,在最近 3 个小时内某酒店的订单量显著下降,等等。由于业务层的监控比较多,因此我们把业务层的监控独立出了一个子系统。业务层监控报警的决策来源于历史数据和未来可能影响系统的因子,通过对历史中的海量数据进行汇总加工和分析,从各个维度给出报警的预期值。
服务层监控和业务层监控都离不开各个应用的支持,有些监控目标会对应用的实现有侵入性,比如需要写业务日志,各个应用根据监控的目标来设计和实现,为业务层监控提供必要的技术支持,因此从系统架构层面开始就需要考虑业务的可用性需求。有了服务健
康状态的监控和报警系统,再辅以调用链追踪系统和日志处理系统,发现和定位故障的时间就比较可控了,可以很快定位绝大部分故障的发生原因,为修复故障提供有力的保障。整个发现定位故障的层次如图 10.12 所示。
图 10.12
10.12 发布管理
最后我想说说发布管理。虽然它不属于基础设施,但是它太重要了,对服务的高可用影响很大。基础设施的工作都做到位了就万事大吉了吗?不,还差一口气——还需要确保最后一公里万无一失,那就是服务的发布管理。最后一公里主要从以下三点着手规划。
容量规划
通过测试报告评估应用的类型是 I/O 密集型、CPU 密集型还是混合型,根据应用的类型申请合理的硬件资源。单台节点最大处理能力是多少?线上有多少容量正在被使用?集群的最大处理能力是多少?这些在发布前都需要合理地评估和测试。
冗余规划
需要考虑异地多可用区部署,部署服务实例不小于 N+2,服务实例硬件配置相同,避免部署实例大小不一。为什么是 N+2 而不是 N+1 呢?这是为了防止在服务更新时挂掉一个服务节点(发布失败是高概率事件)。
发布控制
在线下进行充分测试,能在线下做的测试绝不放在线上做。就像上面所说的,发布失败是高概率事件,所以发布必须要支持回滚,必须拒绝一切没有回滚方案的更新。必须拒绝!
11.6 总结
在微服务架构下可以按功能和职责充分分解服务,解耦依赖,单个服务易于开发和维护,解锁了技术栈,实现了更短的开发迭代周期,促进敏捷开发和持续部署。但我们也要充分认识到微服务有着分布式架构固有特点带来的复杂性,大量服务之间的通信对应用的集成测试、稳定性、运维和监控提出了更高的要求,CAP 理论的约束对数据的一致性也带来了更大的挑战。微服务架构对基础设施的投入很大,简单应用采用单体架构更经济有效,大型复杂应用采用微服务架构才能体现投入的价值。
本文选自《架构宝典》,由中生代技术社区策划出版。
《架构宝典》
出品:中生代技术社区
往期推荐
技术琐话
以分布式设计、架构、体系思想为基础,兼论研发相关的点点滴滴,不限于代码、质量体系和研发管理。